﻿using System;
using JetBrains.Annotations;

namespace StreetPacMan.Server
{
    public interface ILocationCalculator
    {
        bool AreCloseWithinXMeters(double lat1, double lon1, double lat2, double lon2, double meters);
    }
    
    [UsedByIoc]
    public class LocationCalculator : ILocationCalculator
    {
        #region Constants
        //Equatorial radius of the earth from WGS 84 in meters, semi major axis = a
        internal int a = 6378137;
        //flattening = 1/298.257223563 = 0.0033528106647474805
        //first eccentricity squared = e = (2-flattening)*flattening
        internal double e = 0.0066943799901413165;
        //Miles to Meters conversion factor (take inverse for opposite)
        //internal double milesToMeters = 1609.347;
        //Degrees to Radians converstion factor (take inverse for opposite)
        internal double degreesToRadians = Math.PI / 180;
        #endregion

        public bool AreCloseWithinXMeters(double lat1, double lon1, double lat2, double lon2, double meters)
        {
            //lat naught and lon naught are the geodetic parameters in radians
            double lat0 = lat1 * degreesToRadians;
            double lon0 = lon1 * degreesToRadians;

            //Find reference ellipsoid radii
            double Rm = calcMeridionalRadiusOfCurvature(lat0);
            double Rpv = calcRoCinPrimeVertical(lat0);

            //Find boundaries for curvilinear plane that encloses search ellipse
            double latMax = (meters / Rm + lat0) / degreesToRadians;
            double lonMax = (meters / (Rpv * Math.Cos(lat0)) + lon0) / degreesToRadians;
            double latMin = (lat0 - meters / Rm) / degreesToRadians;
            double lonMin = (lon0 - meters / (Rpv * Math.Cos(lat0))) / degreesToRadians;

            return lat2 < latMax && lat2 > latMin && lon2 < lonMax && lon2 > lonMin;
        }

        //public IQueryable<Place> FindNearbyLocations(double latitude, double longitude, double searchRadius, bool isMetric)
        //{
        //    //convert miles to meters
        //    if (isMetric == false)
        //    {
        //        searchRadius = searchRadius * milesToMeters;
        //    }
        //    else
        //    {
        //        searchRadius = searchRadius * 1000;
        //    }

        //    //lat naught and lon naught are the geodetic parameters in radians
        //    double lat0 = latitude * degreesToRadians;
        //    double lon0 = longitude * degreesToRadians;

        //    //Find reference ellipsoid radii
        //    double Rm = calcMeridionalRadiusOfCurvature(lat0);
        //    double Rpv = calcRoCinPrimeVertical(lat0);

        //    //Throw exception if search radius is greater than 1/4 of globe and thus breaks accuracy of model (mostly pertinent for russia, alaska, canada, peru, etc.)
        //    if (Rpv * Math.Cos(lat0) * Math.PI / 2 < searchRadius)
        //    {
        //        throw new ApplicationException("Search radius was too great to achieve an accurate result with this model.");
        //    }

        //    //Find boundaries for curvilinear plane that encloses search ellipse
        //    double latMax = (searchRadius / Rm + lat0) / degreesToRadians;
        //    double lonMax = (searchRadius / (Rpv * Math.Cos(lat0)) + lon0) / degreesToRadians;
        //    double latMin = (lat0 - searchRadius / Rm) / degreesToRadians;
        //    double lonMin = (lon0 - searchRadius / (Rpv * Math.Cos(lat0))) / degreesToRadians;

        //    LocalMeDataContext dx = new LocalMeDataContext();

        //    var places = from p in dx.Places
        //                 where p.Longitude > lonMin && p.Longitude < lonMax &&
        //                       p.Latitude > latMin && p.Latitude < latMax
        //                 select p;

        //    return places;
        //}


        /// <summary>
        /// Calculates the meridional radius of curvature for the reference ellipsoid
        /// </summary>
        /// <remarks>
        /// This is the radius of a circle that fits the earth curvature in the meridian at the latitude chosen.
        /// It is used for latitude, in differentiation of north distances, dN
        /// </remarks>
        /// <param name="lat0">Geodetic latitude in radians</param>
        /// <returns>Length of meridional radius of curvature</returns>
        private double calcMeridionalRadiusOfCurvature(double lat0)
        {
            double Rm = a * (1 - e) / Math.Pow(1 - e * (Math.Pow(Math.Sin(lat0), 2)), 1.5);
            return Rm;
        }

        /// <summary>
        /// Calculates the Radius of curvature in the prime vertical for the reference ellipsoid
        /// </summary>
        /// <remarks>
        /// This is the vector that defines the normal surface to any point on the ellipsoid.  It extends from
        /// from the polar axis to that point.  It is used for the longitude, in differentiation of east distances, dE
        /// </remarks>
        /// <param name="lat0">Geodetic latitude in radians</param>
        /// <returns>Length of radius of curvature in the prime vertical</returns>
        private double calcRoCinPrimeVertical(double lat0)
        {
            double Rn = a / Math.Sqrt(1 - e * Math.Pow(Math.Sin(lat0), 2));
            return Rn;
        }


    }
}